import json

from py2neo import Graph
from config.config_lab import neo4j_uri, neo4j_username, neo4j_password, neo4j_synchronize, neo4j_username_synchronize, neo4j_password_synchronize


# 单例模式
class Neo4jUtil:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
            print("Neo4jUtil instance created ...")
        return cls._instance

    def __init__(self):
        if not hasattr(self, 'graph') or self.graph is None:
            print("Neo4jUtil init ...")
            self.connectDB()
            print("Neo4j connection established ...")

    def connectDB(self):
        try:
            self.graph = Graph(neo4j_uri, auth=(neo4j_username, neo4j_password))
            print("master neo4j connect success")
        except Exception as e:
            print("master neo4j connect failed")

        try:
            self.from_graph = Graph(neo4j_synchronize, auth=(neo4j_username_synchronize, neo4j_password_synchronize))
            print("from neo4j connect success")
        except Exception as e:
            print("from neo4j connect failed")

    # 实体查询
    def matchEntityItem(self, value):
        query = "MATCH (entity1) WHERE entity1.name = $value RETURN entity1"
        parameters = {"value": value}
        result = self.graph.run(query, parameters).data()
        return result

    # 实体模糊查询，返回匹配的实体列表
    def mathEntityListByKeyWord(self, keyword):
        # 使用 Cypher 查询语句进行模糊匹配
        query = "MATCH (entity1) WHERE entity1.name CONTAINS $keyword RETURN entity1.name"
        parameters = {"keyword": keyword}
        result = self.graph.run(query, parameters).data()
        return result

    # 根据实体进行关系查询
    def matchRelByEntity(self, value):
        # 查询实体：不关系方向
        query = "MATCH (entity1) - [rel] - (entity2) WHERE entity1.name = $value RETURN entity1, rel, entity2"
        parameters = {"value": value}
        result = self.graph.run(query, parameters).data()
        return result

    # 根据实体进行多级关系查询
    def matchRelByEntityMultiLevel(self, value, numberOfLevels):
        query = f"MATCH path = (entity1)-[*1..{numberOfLevels}]->(entity2) WHERE entity1.name = $value RETURN path"
        parameters = {"value": value}
        result = self.graph.run(query, parameters).data()
        return result

    # 根据实体进行关系查询， 实体作为关系的起点
    def findRelationByEntityStart(self, entity1):
        query = "MATCH (n1 {name: $entity1})-[rel]->(n2) RETURN n1, rel, n2"
        parameters = {"entity1": entity1}
        result = self.graph.run(query, parameters).data()
        return result

    # 根据实体进行关系查询， 实体作为关系的终点
    def findRelationByEntityEnd(self, entity2):
        query = "MATCH (n1)-[rel]->(n2 {name: $entity2}) RETURN n1, rel, n2"
        parameters = {"entity2": entity2}
        result = self.graph.run(query, parameters).data()
        return result

    # 根据实体和关系查询其它实体, 其它实体作为终点
    def findEntityByEntityAndRelEnd(self, entity, relation):
        cql = '''
            MATCH (n1 {name: $entity})- [rel:%s] -> (n2)
            RETURN n1, rel, n2
        ''' % (relation,)
        result = self.graph.run(cql, entity=entity).data()
        return result

    # 根据实体和关系查询其它实体，其它实体作为起点
    def findEntityByEntityAndRelStart(self, entity, relation):
        cql = '''
            MATCH (n1) <- [rel:%s] - (n2 {name: $entity}) 
                RETURN n1, rel, n2
            ''' %(relation,)
        parameters = {"entity": entity}
        result = self.graph.run(cql, parameters).data()
        return result

    # 根据两个实体查询他们之间存在的关系，没有关系则返回空
    def findRelationshipBetweenEntities(self, entity1, entity2):
        cql = '''
            MATCH (n1)-[rel]-(n2)
            WHERE n1.name = $entity1 AND n2.name = $entity2
            RETURN n1, rel, n2
        '''
        parameters = {"entity1": entity1, "entity2": entity2}
        result = self.graph.run(cql, parameters).data()
        return result

    # 根据两个实体查询它们之间的最短路径
    def findShortRouteByEntities(self, entity1, entity2):
        # 使用参数化查询来防止Cypher注入攻击
        cypher_query = """
            MATCH (p1 {name:$entity1}),(p2 {name:$entity2}), p=allshortestpaths((p1)-[rel*]->(p2)) 
            RETURN p
        """
        parameters = {"entity1": entity1, "entity2": entity2}

        try:
            # 运行参数化查询
            result = self.graph.run(cypher_query, parameters).data()
        except Exception as e:
            # 异常处理
            print(f"Error executing Cypher query: {e}")
            return []

        relationDict = []

        if result:
            for path in result:
                for i in range(len(path['p'])):
                    tmp = {'n1': path['p'][i].start_node, 'n2': path['p'][i].end_node, 'rel': path['p'][i]}
                    relationDict.append(tmp)

        return relationDict

    # 查询数据库中是否存在实体-关系-实体匹配
    def findEntityRelation(self, entity1, relation, entity2):
        cql = '''
            MATCH (n1 {name: $entity1})- [rel:%s] - (n2 {name: $entity2})
            RETURN n1, rel, n2
        ''' % (relation,)

        parameters = {
            'entity1': entity1,
            'entity2': entity2
        }

        answer = self.graph.run(cql, parameters).data()
        return answer

    # 融合的方法，可以查询各种信息
    def search_all(self, entity1="", relation="", entity2="", numberOfLevels=1):
        entity1 =entity1.strip()
        entity2 =entity2.strip()
        relation =relation.strip()
        # 只输入头实体，则查询与这个实体有关系的关系和实体，不区分方向
        if len(entity1) != 0 and len(entity2) == 0 and len(relation) == 0:
            search_result = self.matchRelByEntityMultiLevel(entity1, numberOfLevels)
            # search_result = self.matchRelByEntity(entity1)
            return search_result
        # 只输入尾实体，则查询以尾实体作为终点的关系和实体
        if len(entity1) == 0 and len(entity2) != 0 and len(relation) == 0:
            search_result = self.findRelationByEntityEnd(entity2)
            return search_result
        # 若输入entity1和rel，则输出与entity1具有rel关系的其它实体,其它实体作为终点
        if len(entity1) != 0 and len(entity2) == 0 and len(relation) != 0:
            search_result = self.findEntityByEntityAndRelEnd(entity1, relation)
            return search_result
        # 若输入entity2和rel，则输出头实体
        if len(entity1) == 0 and len(entity2) != 0 and len(relation) != 0:
            search_result = self.findEntityByEntityAndRelStart(entity2, relation)
            return search_result
        # 若输入entity1和entity2，则查询最短路径
        if len(entity1) != 0 and len(entity2) != 0 and len(relation) == 0:
            search_result = self.findShortRouteByEntities(entity1, entity2)
            if len(search_result) > 0:
                return search_result
        # 若输入三元组，则确认是否存在关系
        if len(entity1) != 0 and len(entity2) != 0 and len(relation) != 0:
            search_result = self.findEntityRelation(entity1,relation, entity2)
            if len(search_result) > 0:
                return search_result
        return None

    # 查询所有指向指定节点的入边路径（递归不限层数）
    def matchAllIncomingPathsToEntity(self, entity2):
        query = """
            MATCH path = (source)-[:RELATION*1..]->(target {name: $entity2})
            RETURN path
        """
        parameters = {"entity2": entity2}
        result = self.graph.run(query, parameters).data()
        return result